package com.slipper.config;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.slipper.interceptor.AuthInterceptor;
import com.slipper.util.DateUtil;
import com.slipper.util.Tool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final String[] openArray = new String[] {
            "/",
            "/favicon.ico",
            "/*.html",
            "/captcha",
            "/login",
            "/tokenLogin",
            "/ssoLogin",
            "/logout",
            "/qrcode",
            "/refresh",
            "/setting",
            "/app/**",
            "/area/**",
            "/assets/**",
            "/error/**",
            "/file/**",
            "/image/**",
            "/license/**",
            "/mobile/**",
            "/qrcode/**",
            "/video/**",
            "/v1/**",
            "/business/dictionary/all-dict-data",
            "/business/data-field/list/*",
            "/business/menu/portal-tree",
            "/business/page/*",
            "/business/page-comp/*",
            "/business/*/tree",
            "/codeless/data-model/*/page",
            "/codeless/script/run/*",
            "/icon/list"
    };

    @Value("${open.path:}")
    private String openPath;

    @Value("${allowed.origins:'*'}")
    private String allowedOrigins;

    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("forward:/index.html");
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        CorsRegistration corsRegistration = registry.addMapping("/**");
        if (Tool.isNotBlank(allowedOrigins)) {
            if ("*".equals(allowedOrigins)) {
                corsRegistration.allowedOriginPatterns("*");
            } else {
                corsRegistration.allowedOrigins(allowedOrigins.split(","));
                corsRegistration.allowCredentials(true);
            }
        }
        corsRegistration.allowedHeaders("*");
        corsRegistration.allowedMethods("GET", "POST", "OPTIONS");
        corsRegistration.exposedHeaders(
                        "access-control-allow-headers",
                        "access-control-allow-origin",
                        "access-control-allow-methods",
                        "access-control-max-age",
                        "X-Frame-Options",
                        "Authorization",
                        "Origin",
                        "X-Requested-With",
                        "Content-Type",
                        "Accept",
                        "Access-Token",
                        "Content-Disposition");
        corsRegistration.maxAge(3600);
    }

    /**
     * 排除开放url不进行权限验证
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        List<String> authList = List.of("/**");
        List<String> openList = new ArrayList<>(){{
            addAll(Stream.of(openPath.split(",")).filter(Tool::isNotBlank).toList());
            addAll(Stream.of(openArray).filter(Tool::isNotBlank).toList());
        }};
        logger.info("authUrl: {}, openUrl: {}", authList, openList);
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns(authList)
                .excludePathPatterns(openList);
    }

    /**
     * 使用自定义日期格式化
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for (HttpMessageConverter<?> messageConverter : converters) {
            if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
                ((MappingJackson2HttpMessageConverter) messageConverter).setDefaultCharset(StandardCharsets.UTF_8);

                ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter) messageConverter).getObjectMapper();
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

                SimpleModule simpleModule = new SimpleModule();
                simpleModule.addDeserializer(String.class, new JsonDeserializer<>() {
                    @Override
                    public String deserialize(JsonParser p, DeserializationContext dc)
                            throws IOException {
                        if (p == null || p.getText() == null || p.getText().trim().isEmpty()) {
                            return null;
                        } else {
                            return p.getText().trim();
                        }
                    }
                });
                objectMapper.registerModule(simpleModule);

                objectMapper.setDateFormat(new SimpleDateFormat() {
                    @Override
                    public StringBuffer format(
                            @NonNull Date date,
                            @NonNull StringBuffer toAppendTo,
                            @NonNull FieldPosition fieldPosition) {
                        if (date instanceof java.sql.Date) {
                            return toAppendTo.append(DateUtil.formatDate(date));
                        } else {
                            return toAppendTo.append(DateUtil.formatTime(date));
                        }
                    }

                    @Override
                    public Date parse(String source) throws ParseException {
                        if (source != null && !source.isEmpty()) {
                            return DateUtil.parseDate(source);
                        } else {
                            return null;
                        }
                    }
                });

            } else if (messageConverter instanceof StringHttpMessageConverter converter) {
                converter.setDefaultCharset(StandardCharsets.UTF_8);
            }
        }
    }
}
